package com.gmrz.uaf.protocol.v1.json;

import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.TLVTag;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.ASMRegResponse;
import com.gmrz.uaf.protocol.v1.schema.KeyRegistrationData;
import com.gmrz.util.Convert;
import com.gmrz.util.db.CommonUtils;
import com.google.gson.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.ByteArrayInputStream;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.List;

public class CertASMRegResponseAdapter implements InstanceCreator<ASMRegResponse>,
        JsonSerializer<ASMRegResponse>, JsonDeserializer<ASMRegResponse> {
    private static final Logger LOG = LogManager
            .getLogger(CertASMRegResponseAdapter.class);

    public ASMRegResponse createInstance(Type type) {
        return new ASMRegResponse();
    }

    public JsonElement serialize(ASMRegResponse src, Type typeofSrc,
                                 JsonSerializationContext context) {
        return null;
    }

    public ASMRegResponse deserialize(JsonElement json, Type typeofT,
                                      JsonDeserializationContext context) throws JsonParseException {
        try {
            String encoded = json.getAsString();
            byte[] rawResponse = Convert.fromBase64(encoded);

            ByteBuffer buffer = ByteBuffer.wrap(rawResponse).order(
                    ByteOrder.LITTLE_ENDIAN);
            TLVTag assertionTag = new TLVTag(TLVTag.readTag(buffer));
            byte[] assertionBytes = assertionTag.parseByteArray(buffer);

            ByteBuffer resBuffer = ByteBuffer.wrap(assertionBytes).order(
                    ByteOrder.LITTLE_ENDIAN);

            ASMRegResponse asmRegResponse = new ASMRegResponse();
            KeyRegistrationData krd = null;
            List<Certificate> attestationCerts = new ArrayList<Certificate>();

            byte[] uvi = null;

            while (resBuffer.hasRemaining()){
                byte[] tagByte = new byte[2];
                resBuffer.get(tagByte);
                int tag = Convert.byteArrayToInt(tagByte);
                byte[] lenByte = new byte[2];
                resBuffer.get(lenByte);
                int len = Convert.byteArrayToInt(lenByte);
                byte[] val = new byte[len];
                resBuffer.get(val);
                switch (tag) {
                    case 21764: {
                        byte[] certBytes = val;
                        asmRegResponse.setDevieceCert(certBytes);
                        break;
                    }
                    case 21761: {
                        byte[] p10Bytes = val;
                        asmRegResponse.setP10Bytes(p10Bytes);
                        break;
                    }
                    case 21762: {
                        krd = getKRD(val);
                        asmRegResponse.setKRD(krd);
                        break;
                    }
                    case 21763: {
                        byte[] signatureBytes = val;
                        asmRegResponse.setSignature(signatureBytes);
                        break;
                    }
                    case 21013://uvi
                        uvi = val;
                        break;
                }
            }
            if(null != uvi && uvi.length > 0){
                asmRegResponse.getKRD().setUvi(uvi);
            }
            asmRegResponse.setAttestationType(Constants.AttestationType.ATTESTATION_BASIC_FULL);
            return asmRegResponse;
        } catch (Exception ex) {
            LOG.error("Error parsing the Registration TLV data, ", ex);
            throw new JsonParseException(
                    UAFErrorCode.PROTOCOL_TLV_PARSING_FAILED.toString(), ex);
        }
    }

    public static void main(String[] args){
        CertASMRegResponseAdapter certASMRegResponseAdapter = new CertASMRegResponseAdapter();
        certASMRegResponseAdapter.deserialize(null ,null, null);
    }

    private KeyRegistrationData getKRD(byte[] krdBytes){

        KeyRegistrationData krd = new KeyRegistrationData();
        krd.setReceivedKRDBytes(krdBytes);
        krd.setAuthenticatonMode(Constants.AuthenticationMode.REG_MODE.getValue());
        ByteBuffer krdBuffer = ByteBuffer.wrap(krdBytes).order(
                ByteOrder.LITTLE_ENDIAN);
        while (krdBuffer.hasRemaining()) {
            byte[] tagByte = new byte[2];
            krdBuffer.get(tagByte);
            int tag = Convert.byteArrayToInt(tagByte);
            byte[] lenByte = new byte[2];
            krdBuffer.get(lenByte);
            int len = Convert.byteArrayToInt(lenByte);
            byte[] val;
            switch (tag) {
                case 21766://keyid
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setKeyID(val);
                    break;
                case 20995://FINALCHALLENGE
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setHashedFinalChallenge(val);
                    break;
                case 21013://uvi
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setUvi(val);
                    break;
                case 21014://uvs
                    val = new byte[len];
                    krdBuffer.get(val);
                    if (len > 0 && (len % 32) == 0) {
                        List<byte[]> uvs = new ArrayList<byte[]>(len / 32);
                        for (int i = 0; i < len / 32; i++) {
                            byte[] uvi = new byte[32];
                            System.arraycopy(val, i * 32, uvi, 0, 32);
                            uvs.add(uvi);
                        }
                        krd.setUvs(uvs);
                    }
                    break;
                case 21765://COUNTER
                    val = new byte[len];
                    krdBuffer.get(val);
                    ByteBuffer counterBuf = ByteBuffer.wrap(val).order(
                            ByteOrder.LITTLE_ENDIAN);
                    krd.setRegisterCounter(counterBuf.getShort());
                    if (counterBuf.hasRemaining()) {
                        krd.setSignCounter(counterBuf.getShort());
                    }
                    break;
                case 21003://AAID
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setAAID(new String(val));
                    break;
                case 21015://PUBKEY
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setPubKey(val);
                    break;
                case 21005://TAG_CERT_PRE_AUTH_CODE
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setPreAuthCode(val);
                    break;
                case 21025://TAG_CERT_PRE_REFERENCE_CODE
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setRefCode(val);
                    break;
                case 21016://TAG_FIDO_PUBKEY
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setFidoPubkey(val);
                    break;
                case 21252://TAG_DEVICEID
                    val = new byte[len];
                    krdBuffer.get(val);
                    krd.setDeviceID(Convert.toBase64(val));
                    break;
            }
        }
        return krd;
    }
}
